package cn.backflow.utils;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.io.*;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * File optations
 * Created by Taeyeon on 2015/11/27.
 */
public abstract class Files {

    public static String newFilenameWithExt(String origname, String newname) {
        return newname + "." + Strings.extension(origname);
    }

    /**
     * 创建文件到指定路径 (如果目标路径不存在，会先创建父路径)
     *
     * @param dir      要保存的路径
     * @param filename 文件名
     * @return 最终创建的文件
     */
    public static File touch(String dir, String filename) {
        File file = new File(dir, filename);
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();
        return file;
    }

    public static void touch(File file) throws IOException {
        if (!file.exists()) {
            OutputStream out = openOutputStream(file);
            closeQuietly(out);
        }
        boolean success = file.setLastModified(System.currentTimeMillis());
        if (!success) {
            throw new IOException("Unable to set the last modification time for " + file);
        }
    }

    public static void closeQuietly(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (IOException ioe) {
            // ignore
        }
    }

    //-----------------------------------------------------------------------

    /**
     * Opens a {@link FileOutputStream} for the specified file, checking and
     * creating the parent directory if it does not exist.
     * <p>
     * At the end of the method either the stream will be successfully opened,
     * or an exception will have been thrown.
     * <p>
     * The parent directory will be created if it does not exist.
     * The file will be created if it does not exist.
     * An exception is thrown if the file object exists but is a directory.
     * An exception is thrown if the file exists but cannot be written to.
     * An exception is thrown if the parent directory cannot be created.
     *
     * @param file the file to open for output, must not be <code>null</code>
     * @return a new {@link FileOutputStream} for the specified file
     * @throws IOException if the file object is a directory
     * @throws IOException if the file cannot be written to
     * @throws IOException if a parent directory needs creating but that fails
     * @since 1.3
     */
    public static FileOutputStream openOutputStream(File file) throws IOException {
        return openOutputStream(file, false);
    }

    /**
     * Opens a {@link FileOutputStream} for the specified file, checking and
     * creating the parent directory if it does not exist.
     * <p>
     * At the end of the method either the stream will be successfully opened,
     * or an exception will have been thrown.
     * <p>
     * The parent directory will be created if it does not exist.
     * The file will be created if it does not exist.
     * An exception is thrown if the file object exists but is a directory.
     * An exception is thrown if the file exists but cannot be written to.
     * An exception is thrown if the parent directory cannot be created.
     *
     * @param file   the file to open for output, must not be <code>null</code>
     * @param append if <code>true</code>, then bytes will be added to the
     *               end of the file rather than overwriting
     * @return a new {@link FileOutputStream} for the specified file
     * @throws IOException if the file object is a directory
     * @throws IOException if the file cannot be written to
     * @throws IOException if a parent directory needs creating but that fails
     * @since 2.1
     */
    public static FileOutputStream openOutputStream(File file, boolean append) throws IOException {
        if (file.exists()) {
            if (file.isDirectory()) {
                throw new IOException("File '" + file + "' exists but is a directory");
            }
            if (!file.canWrite()) {
                throw new IOException("File '" + file + "' cannot be written to");
            }
        } else {
            File parent = file.getParentFile();
            if (parent != null) {
                if (!parent.mkdirs() && !parent.isDirectory()) {
                    throw new IOException("Directory '" + parent + "' could not be created");
                }
            }
        }
        return new FileOutputStream(file, append);
    }

    /**
     * Gets image dimensions for given file
     *
     * @param image image file
     * @return dimensions of image
     * @throws IOException if the file is not a known image
     */
    public static Dimension getImageDimension(File image) throws IOException {
        int pos = image.getName().lastIndexOf(".");
        if (pos == -1)
            throw new IOException("No extension for file: " + image.getAbsolutePath());
        String suffix = image.getName().substring(pos + 1);
        Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
        if (iter.hasNext()) {
            ImageReader reader = iter.next();
            try {
                ImageInputStream stream = new FileImageInputStream(image);
                reader.setInput(stream);
                int width = reader.getWidth(reader.getMinIndex());
                int height = reader.getHeight(reader.getMinIndex());
                return new Dimension(width, height);
            } catch (IOException e) {
                System.err.println("Error reading: " + image.getAbsolutePath());
                e.printStackTrace();
            } finally {
                reader.dispose();
            }
        }
        throw new IOException("Not a known image file: " + image.getAbsolutePath());
    }

    /**
     * 获取单个文件的MD5值！
     */
    public static String getFileMD5(File file) {
        if (!file.isFile()) {
            return null;
        }
        MessageDigest digest;
        FileInputStream in;
        byte buffer[] = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }

    /**
     * 以指定的文件编码根据文件路径和内容创建文件
     *
     * @param filePath 文件路径
     * @param content  文本内容
     */
    public static void createFile(String filePath, String content, String charset) throws Exception {
        File file = new File(filePath);
        Writer out = new OutputStreamWriter(new FileOutputStream(file), charset);
        out.write(content);
        out.flush();
        out.close();
    }

    /**
     * 以指定编码格式读取指定的文件
     *
     * @param path    文件路径
     * @param charset 字符编码
     */
    public static String readFile(String path, String charset) throws Exception {
        return readFile(new FileInputStream(path), charset);
    }

    /**
     * 以指定编码格式读取指定的文件
     *
     * @param in      文件输入流
     * @param charset 字符集
     */
    public static String readFile(InputStream in, String charset) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(in, charset));
            StringBuilder sb = new StringBuilder("");
            String data;
            while ((data = br.readLine()) != null) {
                sb.append(data.trim()).append("\n");
            }
            return sb.toString().trim();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null)
                    br.close();
            } catch (IOException ignored) {
            }
        }
        return "";
    }

    /**
     * <pre>
     * 压缩文件 eg:
     * List<String> list =new ArrayList<String>();
     * list.add("d://1.txt");
     * list.add("d://2.txt");
     * list.add("d://基于REST风格的RBAC模型研究.pdf");
     * Files.files2Zip(list, "d://test.zip");
     * </pre>
     *
     * @param filelist ,要压缩的文件源 ，路径集合
     * @param zippath  ,压缩到目标地址
     */
    public static void filesToZip(List<String> filelist, String zippath) {
        File zipFile = new File(zippath); // 最终打包的压缩包
        ZipOutputStream zipout = null;
        FileInputStream input = null;
        BufferedInputStream bufferStream = null;
        try {
            zipout = new ZipOutputStream(new FileOutputStream(zipFile));//用这个构造最终压缩包的输出流
            byte[] buffer = new byte[1024 * 10];    //读写缓冲区
            int len;
            for (String s : filelist) {
                File file = new File(s);
                input = new FileInputStream(file);
                ZipEntry zipEntry = new ZipEntry(file.getName()); // 压缩条目不是具体独立的文件，而是压缩包文件列表中的列表项，称为条目，就像索引一样
                zipout.putNextEntry(zipEntry); // 定位到该压缩条目位置，开始写入文件到压缩包中
                bufferStream = new BufferedInputStream(input, 1024 * 10); // 输入缓冲流
                while ((len = bufferStream.read(buffer)) != -1)
                    zipout.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != bufferStream)
                    bufferStream.close();
                if (null != zipout)
                    zipout.close();
                if (null != input)
                    input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static ByteArrayOutputStream getZipOutputStream(Map<String, String> map) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);
        map.forEach((name, url) -> {
            try {
                ByteArrayOutputStream out = getOutputStemFromURL(url);
                zos.putNextEntry(new ZipEntry(name));
                zos.write(out.toByteArray());
                zos.closeEntry();
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
        zos.close();
        return baos;
    }

    // 根据文件链接把文件下载下来并且转成字节码
    public static ByteArrayOutputStream getOutputStemFromURL(String url) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setConnectTimeout(10000);
        conn.setRequestMethod("GET");
        conn.setDoInput(true);
        try (InputStream is = conn.getInputStream()) {
            if (conn.getResponseCode() == 200) {
                byte[] buffer = new byte[2048];
                int length;
                while ((length = is.read(buffer)) != -1) {
                    baos.write(buffer, 0, length);
                }
                baos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
        }
        return baos;
    }
}